package com.yint.http;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import com.yint.http.call.Call;
import com.yint.http.call.CallBack;
import com.yint.http.call.DefaultCall;
import com.yint.http.call.HttpsCall;
import com.yint.http.request.Request;
import com.yint.http.request.RequestWrapper;
import com.yint.http.response.Response;
import com.yint.http.values.PartValue;

/**
 * 简单封装 HTTP请求连接对象的创建，请求参数和请求头的写入
 * 
 * @author 殷田
 *
 */
public class EasyHttp
{
	private ThreadPool threadPool = new ThreadPool();
	public final RequestWrapper wrapper = new RequestWrapper();

	private SSLSocketFactory factory;

	public EasyHttp()
	{

	}

	/**
	 * 获取请求呼叫 用于执行http或者https请求
	 * 
	 * @param request
	 * @return
	 * @throws IOException
	 */
	public Call newCall( Request request ) throws IOException
	{
		if ( request == null )
		{
			return null;
		}
		URL url = new URL( request.getUrlStr() );
		if ( "https".equalsIgnoreCase( url.getProtocol() ) )
		{
			if ( factory != null )
			{
				request.setSSLSocketFactory( factory );
			}
			return new HttpsCall( request );
		}
		return new DefaultCall( request );
	}

	/**
	 * 同步的Get请求
	 *
	 * @param url
	 * @return Response
	 * @throws IOException
	 */
	public Response getSyn( String url ) throws IOException
	{
		return doExecute( wrapper.get( url ) );
	}

	/**
	 * 异步的get请求
	 *
	 * @param url
	 * @param callback
	 * @throws IOException
	 */
	public void getAsyn( String url, CallBack callback )
	{
		doEnqueue( wrapper.get( url ), callback );
	}

	/**
	 * 同步的Post请求
	 *
	 * @param url
	 * @param params
	 *            post的参数
	 * @return
	 * @throws IOException
	 */
	public Response postSyn( String url, List< PartValue > params ) throws IOException
	{
		return doExecute( wrapper.post( url, params ) );
	}

	/**
	 * 同步的Post请求
	 *
	 * @param url
	 * @param params
	 *            post的参数
	 * @return
	 * @throws IOException
	 */
	public Response postSyn( String url, String contextType, List< PartValue > params )
					throws IOException
	{
		return doExecute( wrapper.post( url, contextType, params ) );
	}

	/**
	 * 异步的Post请求 参数为String类型
	 * 
	 * @param url
	 * @param param
	 * @param contextType
	 * @param callback
	 * @throws IOException
	 */
	public void postAsyn( String url, String param, String contextType, CallBack callback )
	{
		doEnqueue( wrapper.post( url, param, contextType ), callback );
	}

	/**
	 * 同步的Post请求 参数为String类型
	 * 
	 * @param url
	 * @param param
	 * @param contextType
	 * @param callback
	 * @throws IOException
	 */
	public Response postSyn( String url, String param, String contextType ) throws IOException
	{
		return doExecute( wrapper.post( url, param, contextType ) );
	}

	/**
	 * 异步的Post请求
	 *
	 * @param url
	 * @param params
	 *            post的参数
	 * @return
	 * @throws IOException
	 */
	public void postAsyn( String url, String contextType, List< PartValue > params,
					CallBack callback )
	{
		doEnqueue( wrapper.post( url, contextType, params ), callback );
	}

	/**
	 * 异步的Post请求
	 *
	 * @param url
	 * @param params
	 *            post的参数
	 * @return
	 * @throws IOException
	 */
	public void postAsyn( String url, List< PartValue > params, CallBack callback )
	{
		postAsyn( url, null, params, callback );
	}

	/**
	 * 异步基于post的文件上传，单文件不带参数上传
	 *
	 * @param url
	 * @param callback
	 * @param file
	 * @param fileName
	 * @throws IOException
	 */
	public void postAsyn( String url, File file, String fileName, CallBack callback )
	{
		postAsyn( url, null, file, fileName, callback );
	}

	/**
	 * 异步基于post的文件上传，单文件且携带其他form参数上传
	 *
	 * @param url
	 * @param callback
	 * @param file
	 * @param fileName
	 * @param params
	 * @throws IOException
	 */
	public void postAsyn( String url, List< PartValue > params, File file, String fileName,
					CallBack callback )
	{
		doEnqueue( wrapper.post( url, params, file, fileName ), callback );
	}

	public Response doExecute( Request request ) throws IOException
	{
		Response response = newCall( request ).execute();
		saveCookie( response );
		return response;
	}

	public void doEnqueue( final Request request, final CallBack callBack )
	{
		Call call = null;
		try
		{
			call = newCall( request );
			call.enqueue( new CallBack()
			{

				@Override
				public void onResponse( Call call, Response response ) throws IOException
				{
					saveCookie( response );
					if ( callBack != null )
					{
						callBack.onResponse( call, response );
					}
				}

				@Override
				public void onFailure( Call call, IOException e )
				{
					if ( callBack != null )
					{
						callBack.onFailure( call, e );
					}
				}
			} );
		}
		catch ( IOException e )
		{
			if ( callBack != null )
			{
				callBack.onFailure( call, e );
			}
		}
	}

	public Response doExecute( Request request, int times )
	{
		Response response = null;
		for ( int i = 0; i < times; i++ )
		{
			try
			{
				response = doExecute( request );
				if ( HttpURLConnection.HTTP_OK == response.getCode() )
				{
					break;
				}
				wrapper.resetCookies( request );
				Thread.sleep( 1000 );
			}
			catch ( Exception e )
			{
				e.printStackTrace();
			}
		}
		return response;
	}

	public void doEnqueue( final Request request, final int times, final CallBack callBack )
	{
		threadPool.addCacheTask( new Runnable()
		{

			@Override
			public void run()
			{
				Call call = null;
				try
				{
					call = newCall( request );
					Response response = null;
					for ( int i = 0; i < times; i++ )
					{
						try
						{
							response = call.execute();
							if ( HttpURLConnection.HTTP_OK == response.getCode() )
							{
								saveCookie( response );
								break;
							}
							wrapper.resetCookies( request );
							Thread.sleep( 1000 );
						}
						catch ( Exception e )
						{
							e.printStackTrace();
						}
					}
					if ( callBack != null )
					{
						callBack.onResponse( call, response );
					}
				}
				catch ( IOException e )
				{
					e.printStackTrace();
					if ( callBack != null )
					{
						callBack.onFailure( call, e );
					}
				}
			}
		} );
	}

	private void saveCookie( Response response )
	{
		String cookies = response.getHeaderField( "set-cookie" );
		if ( cookies != null && cookies.length() > 0 )
		{
			wrapper.setCookies( cookies );
		}
	}
	
	public void setCookies(String cookies)
	{
		wrapper.setCookies( cookies );
	}
	
	public String getCookies()
	{
		return wrapper.getCookies();
	}

	public void setSSLSocketFactory( SSLSocketFactory factory )
	{
		this.factory = factory;
	}

	public void setConnectTimeout( int time )
	{
		wrapper.setConnectTimeout( time );
	}

	public void setReadTimeout( int time )
	{
		wrapper.setReadTimeout( time );
	}

	/**
	 * 装载证书 得到一个 SSLSocketFactory
	 * 
	 * @param certificates
	 *            证书的流
	 * @return
	 */
	public static SSLSocketFactory getSSLSocketFactory( InputStream[] certificates )
	{
		try
		{
			// 证书工厂。此处指明证书的类型
			CertificateFactory certificateFactory = CertificateFactory.getInstance( "X.509" );
			// 创建一个证书库
			KeyStore keyStore = KeyStore.getInstance( KeyStore.getDefaultType() );
			keyStore.load( null, null );
			int index = 0;
			for ( InputStream certificate : certificates )
			{
				try
				{
					String certificateAlias = "ca" + ( index++ );
					// 将证书导入证书库
					keyStore.setCertificateEntry( certificateAlias,
									certificateFactory.generateCertificate( certificate ) );
					if ( certificate != null )
						certificate.close();
				}
				catch ( IOException e )
				{
					e.printStackTrace();
				}
			}
			// 取得SSL的SSLContext实例
			TrustManagerFactory trustManagerFactory = TrustManagerFactory
							.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
			trustManagerFactory.init( keyStore );
			SSLContext sslContext = SSLContext.getInstance( "TLS" );
			sslContext.init( null, trustManagerFactory.getTrustManagers(), null );
			return sslContext.getSocketFactory();
		}
		catch ( Exception e )
		{
			e.printStackTrace();
		}
		return null;
	}

}
